home *** CD-ROM | disk | FTP | other *** search
/ Maximum CD 2009 May / maximum-cd-2009-05.iso / DiscContents / Firefox Setup 3.0.6.exe / nonlocalized / chrome / browser.jar / content / browser / places / places.js < prev    next >
Encoding:
JavaScript  |  2008-05-07  |  44.1 KB  |  1,244 lines

  1. /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2. /* ***** BEGIN LICENSE BLOCK *****
  3.  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  4.  *
  5.  * The contents of this file are subject to the Mozilla Public License Version
  6.  * 1.1 (the "License"); you may not use this file except in compliance with
  7.  * the License. You may obtain a copy of the License at
  8.  * http://www.mozilla.org/MPL/
  9.  *
  10.  * Software distributed under the License is distributed on an "AS IS" basis,
  11.  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  12.  * for the specific language governing rights and limitations under the
  13.  * License.
  14.  *
  15.  * The Original Code is Mozilla Places Organizer.
  16.  *
  17.  * The Initial Developer of the Original Code is Google Inc.
  18.  * Portions created by the Initial Developer are Copyright (C) 2005-2006
  19.  * the Initial Developer. All Rights Reserved.
  20.  *
  21.  * Contributor(s):
  22.  *   Ben Goodger <beng@google.com>
  23.  *   Annie Sullivan <annie.sullivan@gmail.com>
  24.  *   Asaf Romano <mano@mozilla.com>
  25.  *   Ehsan Akhgari <ehsan.akhgari@gmail.com>
  26.  *
  27.  * Alternatively, the contents of this file may be used under the terms of
  28.  * either the GNU General Public License Version 2 or later (the "GPL"), or
  29.  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  30.  * in which case the provisions of the GPL or the LGPL are applicable instead
  31.  * of those above. If you wish to allow use of your version of this file only
  32.  * under the terms of either the GPL or the LGPL, and not to allow others to
  33.  * use your version of this file under the terms of the MPL, indicate your
  34.  * decision by deleting the provisions above and replace them with the notice
  35.  * and other provisions required by the GPL or the LGPL. If you do not delete
  36.  * the provisions above, a recipient may use your version of this file under
  37.  * the terms of any one of the MPL, the GPL or the LGPL.
  38.  *
  39.  * ***** END LICENSE BLOCK ***** */
  40.  
  41. var PlacesOrganizer = {
  42.   _places: null,
  43.   _content: null,
  44.  
  45.   _initFolderTree: function() {
  46.     var leftPaneRoot = PlacesUIUtils.leftPaneFolderId;
  47.     this._places.place = "place:excludeItems=1&expandQueries=0&folder=" + leftPaneRoot;
  48.   },
  49.  
  50.   selectLeftPaneQuery: function PO_selectLeftPaneQuery(aQueryName) {
  51.     var itemId = PlacesUIUtils.leftPaneQueries[aQueryName];
  52.     this._places.selectItems([itemId]);
  53.     // Forcefully expand all-bookmarks
  54.     if (aQueryName == "AllBookmarks")
  55.       asContainer(this._places.selectedNode).containerOpen = true;
  56.   },
  57.  
  58.   init: function PO_init() {
  59.     this._places = document.getElementById("placesList");
  60.     this._content = document.getElementById("placeContent");
  61.     this._initFolderTree();
  62.  
  63.     var leftPaneSelection = "AllBookmarks"; // default to all-bookmarks
  64.     if ("arguments" in window && window.arguments.length > 0)
  65.       leftPaneSelection = window.arguments[0];
  66.  
  67.     this.selectLeftPaneQuery(leftPaneSelection);
  68.     // clear the back-stack
  69.     this._backHistory.splice(0);
  70.     document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
  71.  
  72.     var view = this._content.treeBoxObject.view;
  73.     if (view.rowCount > 0)
  74.       view.selection.select(0);
  75.  
  76.     this._content.focus();
  77.  
  78.     // Set up the search UI.
  79.     PlacesSearchBox.init();
  80.  
  81. //@line 85 "/e/fx19rel/WINNT_5.2_Depend/mozilla/browser/components/places/content/places.js"
  82.  
  83.     window.addEventListener("AppCommand", this, true);
  84. //@line 107 "/e/fx19rel/WINNT_5.2_Depend/mozilla/browser/components/places/content/places.js"
  85.  
  86.     // remove the "Properties" context-menu item, we've our own details pane
  87.     document.getElementById("placesContext")
  88.             .removeChild(document.getElementById("placesContext_show:info"));
  89.   },
  90.  
  91.   QueryInterface: function PO_QueryInterface(aIID) {
  92.     if (aIID.equals(Components.interfaces.nsIDOMEventListener) ||
  93.         aIID.equals(Components.interfaces.nsISupports))
  94.       return this;
  95.  
  96.     throw Components.results.NS_NOINTERFACE;
  97.   },
  98.  
  99.   handleEvent: function PO_handleEvent(aEvent) {
  100.     if (aEvent.type != "AppCommand")
  101.       return;
  102.  
  103.     aEvent.stopPropagation();
  104.     switch (aEvent.command) {
  105.       case "Back":
  106.         if (this._backHistory.length > 0)
  107.           this.back();
  108.         break;
  109.       case "Forward":
  110.         if (this._forwardHistory.length > 0)
  111.           this.forward();
  112.         break;
  113.       case "Search":
  114.         PlacesSearchBox.findAll();
  115.         break;
  116.     }
  117.   },
  118.  
  119.   destroy: function PO_destroy() {
  120.   },
  121.  
  122.   _location: null,
  123.   get location() {
  124.     return this._location;
  125.   },
  126.  
  127.   set location(aLocation) {
  128.     if (!aLocation || this._location == aLocation)
  129.       return aLocation;
  130.  
  131.     if (this.location) {
  132.       this._backHistory.unshift(this.location);
  133.       this._forwardHistory.splice(0);
  134.     }
  135.  
  136.     this._location = aLocation;
  137.     this._places.selectPlaceURI(aLocation);
  138.  
  139.     if (!this._places.hasSelection) {
  140.       // If no node was found for the given place: uri, just load it directly
  141.       this._content.place = aLocation;
  142.     }
  143.     this.onContentTreeSelect();
  144.  
  145.     // update navigation commands
  146.     if (this._backHistory.length == 0)
  147.       document.getElementById("OrganizerCommand:Back").setAttribute("disabled", true);
  148.     else
  149.       document.getElementById("OrganizerCommand:Back").removeAttribute("disabled");
  150.     if (this._forwardHistory.length == 0)
  151.       document.getElementById("OrganizerCommand:Forward").setAttribute("disabled", true);
  152.     else
  153.       document.getElementById("OrganizerCommand:Forward").removeAttribute("disabled");
  154.  
  155.     return aLocation;
  156.   },
  157.  
  158.   _backHistory: [],
  159.   _forwardHistory: [],
  160.  
  161.   back: function PO_back() {
  162.     this._forwardHistory.unshift(this.location);
  163.     var historyEntry = this._backHistory.shift();
  164.     this._location = null;
  165.     this.location = historyEntry;
  166.   },
  167.   forward: function PO_forward() {
  168.     this._backHistory.unshift(this.location);
  169.     var historyEntry = this._forwardHistory.shift();
  170.     this._location = null;
  171.     this.location = historyEntry;
  172.   },
  173.  
  174.   /**
  175.    * Called when a place folder is selected in the left pane.
  176.    * @param   resetSearchBox
  177.    *          true if the search box should also be reset, false if it should
  178.    *          be left alone.
  179.    */
  180.   onPlaceSelected: function PO_onPlaceSelected(resetSearchBox) {
  181.     // Don't change the right-hand pane contents when there's no selection
  182.     if (!this._places.hasSelection)
  183.       return;
  184.  
  185.     var node = this._places.selectedNode;
  186.     var queries = asQuery(node).getQueries({});
  187.  
  188.     // Items are only excluded on the left pane
  189.     var options = node.queryOptions.clone();
  190.     options.excludeItems = false;
  191.     var placeURI = PlacesUtils.history.queriesToQueryString(queries, queries.length, options);
  192.  
  193.     // Update the right-pane contents.
  194.     // We must update also if the user clears the search box, in that case
  195.     // we are called with resetSearchBox == false.
  196.     if (this._content.place != placeURI || !resetSearchBox) {
  197.       this._content.place = placeURI;
  198.  
  199.       // Update the back/forward buttons.
  200.       this.location = node.uri;
  201.     }
  202.  
  203.     // Make sure the search UI is hidden.
  204.     PlacesSearchBox.hideSearchUI();
  205.     if (resetSearchBox) {
  206.       var searchFilter = document.getElementById("searchFilter");
  207.       searchFilter.reset();
  208.     }
  209.  
  210.     this._setSearchScopeForNode(node);
  211.     if (this._places.treeBoxObject.focused)
  212.       this._fillDetailsPane(node);
  213.   },
  214.  
  215.   /**
  216.    * Sets the search scope based on node's properties
  217.    * @param   aNode
  218.    *          the node to set up scope from
  219.    */
  220.   _setSearchScopeForNode: function PO__setScopeForNode(aNode) {
  221.     var scopeBarFolder = document.getElementById("scopeBarFolder");
  222.     var itemId = aNode.itemId;
  223.     if (PlacesUtils.nodeIsHistoryContainer(aNode) ||
  224.         itemId == PlacesUIUtils.leftPaneQueries["History"]) {
  225.       scopeBarFolder.disabled = true;
  226.       var folders = [];
  227.       var filterCollection = "history";
  228.       var scopeButton = "scopeBarHistory";
  229.     }
  230.     else if (PlacesUtils.nodeIsFolder(aNode) &&
  231.              itemId != PlacesUIUtils.leftPaneQueries["AllBookmarks"] &&
  232.              itemId != PlacesUIUtils.leftPaneQueries["Tags"] &&
  233.              aNode.parent.itemId != PlacesUIUtils.leftPaneQueries["Tags"]) {
  234.       // enable folder scope
  235.       scopeBarFolder.disabled = false;
  236.       var folders = [PlacesUtils.getConcreteItemId(aNode)];
  237.       var filterCollection = "collection";
  238.       var scopeButton = "scopeBarFolder";
  239.     }
  240.     else {
  241.       // default to All Bookmarks
  242.       scopeBarFolder.disabled = true;
  243.       var folders = [];
  244.       var filterCollection = "bookmarks";
  245.       var scopeButton = "scopeBarAll";
  246.     }
  247.  
  248.     // set search scope
  249.     PlacesSearchBox.folders = folders;
  250.     PlacesSearchBox.filterCollection = filterCollection;
  251.  
  252.     // update scope bar active child
  253.     var scopeBar = document.getElementById("organizerScopeBar");
  254.     var child = scopeBar.firstChild;
  255.     while (child) {
  256.       if (child.getAttribute("id") != scopeButton)
  257.         child.removeAttribute("checked");
  258.       else
  259.         child.setAttribute("checked", "true");
  260.       child = child.nextSibling;
  261.     }
  262.  
  263.     // Update the "Find in <current collection>" command
  264.     var findCommand = document.getElementById("OrganizerCommand_find:current");
  265.     var findLabel = PlacesUIUtils.getFormattedString("findInPrefix", [aNode.title]);
  266.     findCommand.setAttribute("label", findLabel);
  267.   },
  268.  
  269.   /**
  270.    * Handle clicks on the tree. If the user middle clicks on a URL, load that
  271.    * URL according to rules. Single clicks or modified clicks do not result in
  272.    * any special action, since they're related to selection.
  273.    * @param   aEvent
  274.    *          The mouse event.
  275.    */
  276.   onTreeClick: function PO_onTreeClick(aEvent) {
  277.     if (aEvent.target.localName != "treechildren")
  278.       return;
  279.  
  280.     var currentView = aEvent.currentTarget;
  281.     var selectedNode = currentView.selectedNode;
  282.     if (selectedNode && aEvent.button == 1) {
  283.       if (PlacesUtils.nodeIsURI(selectedNode))
  284.         PlacesUIUtils.openNodeWithEvent(selectedNode, aEvent);
  285.       else if (PlacesUtils.nodeIsContainer(selectedNode)) {
  286.         // The command execution function will take care of seeing the
  287.         // selection is a folder/container and loading its contents in
  288.         // tabs for us.
  289.         PlacesUIUtils.openContainerNodeInTabs(selectedNode);
  290.       }
  291.     }
  292.   },
  293.  
  294.   /**
  295.    * Handle focus changes on the trees.
  296.    * When moving focus between panes we should update the details pane contents.
  297.    * @param   aEvent
  298.    *          The mouse event.
  299.    */
  300.   onTreeFocus: function PO_onTreeFocus(aEvent) {
  301.     var currentView = aEvent.currentTarget;
  302.     var selectedNode = currentView.selectedNode;
  303.     this._fillDetailsPane(selectedNode);
  304.   },
  305.  
  306.   openFlatContainer: function PO_openFlatContainerFlatContainer(aContainer) {
  307.     if (aContainer.itemId != -1)
  308.       this._places.selectItems([aContainer.itemId]);
  309.     else if (PlacesUtils.nodeIsQuery(aContainer))
  310.       this._places.selectPlaceURI(aContainer.uri);
  311.   },
  312.  
  313.   openSelectedNode: function PU_openSelectedNode(aEvent) {
  314.     PlacesUIUtils.openNodeWithEvent(this._content.selectedNode, aEvent);
  315.   },
  316.  
  317.   /**
  318.    * Returns the options associated with the query currently loaded in the
  319.    * main places pane.
  320.    */
  321.   getCurrentOptions: function PO_getCurrentOptions() {
  322.     return asQuery(this._content.getResult().root).queryOptions;
  323.   },
  324.  
  325.   /**
  326.    * Returns the queries associated with the query currently loaded in the
  327.    * main places pane.
  328.    */
  329.   getCurrentQueries: function PO_getCurrentQueries() {
  330.     return asQuery(this._content.getResult().root).getQueries({});
  331.   },
  332.  
  333.   /**
  334.    * Show the migration wizard for importing from a file.
  335.    */
  336.   importBookmarks: function PO_import() {
  337.     // XXX: ifdef it to be non-modal (non-"sheet") on mac (see bug 259039)
  338.     var features = "modal,centerscreen,chrome,resizable=no";
  339.  
  340.     // The migrator window will set this to true when it closes, if the user
  341.     // chose to migrate from a specific file.
  342.     window.fromFile = false;
  343.     openDialog("chrome://browser/content/migration/migration.xul",
  344.                "migration", features, "bookmarks");
  345.     if (window.fromFile)
  346.       this.importFromFile();
  347.   },
  348.  
  349.   /**
  350.    * Open a file-picker and import the selected file into the bookmarks store
  351.    */
  352.   importFromFile: function PO_importFromFile() {
  353.     var fp = Cc["@mozilla.org/filepicker;1"].
  354.              createInstance(Ci.nsIFilePicker);
  355.     fp.init(window, PlacesUIUtils.getString("SelectImport"),
  356.             Ci.nsIFilePicker.modeOpen);
  357.     fp.appendFilters(Ci.nsIFilePicker.filterHTML);
  358.     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
  359.       if (fp.file) {
  360.         var importer = Cc["@mozilla.org/browser/places/import-export-service;1"].
  361.                        getService(Ci.nsIPlacesImportExportService);
  362.         var file = fp.file.QueryInterface(Ci.nsILocalFile);
  363.         importer.importHTMLFromFile(file, false);
  364.       }
  365.     }
  366.   },
  367.  
  368.   /**
  369.    * Allows simple exporting of bookmarks.
  370.    */
  371.   exportBookmarks: function PO_exportBookmarks() {
  372.     var fp = Cc["@mozilla.org/filepicker;1"].
  373.              createInstance(Ci.nsIFilePicker);
  374.     fp.init(window, PlacesUIUtils.getString("EnterExport"),
  375.             Ci.nsIFilePicker.modeSave);
  376.     fp.appendFilters(Ci.nsIFilePicker.filterHTML);
  377.     fp.defaultString = "bookmarks.html";
  378.     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
  379.       var exporter = Cc["@mozilla.org/browser/places/import-export-service;1"].
  380.                      getService(Ci.nsIPlacesImportExportService);
  381.       exporter.exportHTMLToFile(fp.file);
  382.     }
  383.   },
  384.  
  385.   /**
  386.    * Populates the restore menu with the dates of the backups available.
  387.    */
  388.   populateRestoreMenu: function PO_populateRestoreMenu() {
  389.     var restorePopup = document.getElementById("fileRestorePopup");
  390.  
  391.     // remove existing menu items
  392.     // last item is the restoreFromFile item
  393.     while (restorePopup.childNodes.length > 1)
  394.       restorePopup.removeChild(restorePopup.firstChild);
  395.  
  396.     // get list of files
  397.     var fileList = [];
  398.     var files = this.bookmarksBackupDir.directoryEntries;
  399.     while (files.hasMoreElements()) {
  400.       var f = files.getNext().QueryInterface(Ci.nsIFile);
  401.       if (!f.isHidden() && f.leafName.match(/^bookmarks-.+json$/))
  402.         fileList.push(f);
  403.     }
  404.  
  405.     fileList.sort(function PO_fileList_compare(a, b) {
  406.       return b.lastModifiedTime - a.lastModifiedTime;
  407.     });
  408.  
  409.     if (fileList.length == 0)
  410.       return;
  411.  
  412.     // populate menu
  413.     for (var i = 0; i < fileList.length; i++) {
  414.       var m = restorePopup.insertBefore
  415.         (document.createElement("menuitem"),
  416.          document.getElementById("restoreFromFile"));
  417.       var dateStr = fileList[i].leafName.replace("bookmarks-", "").
  418.         replace(/\.json$/, "");
  419.       if (!dateStr.length)
  420.         dateStr = fileList[i].leafName;
  421.       m.setAttribute("label", dateStr);
  422.       m.setAttribute("value", fileList[i].leafName);
  423.       m.setAttribute("oncommand",
  424.                      "PlacesOrganizer.onRestoreMenuItemClick(this);");
  425.     }
  426.     restorePopup.insertBefore(document.createElement("menuseparator"),
  427.                               document.getElementById("restoreFromFile"));
  428.   },
  429.  
  430.   /**
  431.    * Called when a menuitem is selected from the restore menu.
  432.    */
  433.   onRestoreMenuItemClick: function PO_onRestoreMenuItemClick(aMenuItem) {
  434.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  435.                  getService(Ci.nsIProperties);
  436.     var bookmarksFile = dirSvc.get("ProfD", Ci.nsIFile);
  437.     bookmarksFile.append("bookmarkbackups");
  438.     bookmarksFile.append(aMenuItem.getAttribute("value"));
  439.     if (!bookmarksFile.exists())
  440.       return;
  441.     this.restoreBookmarksFromFile(bookmarksFile);
  442.   },
  443.  
  444.   /**
  445.    * Called when 'Choose File...' is selected from the restore menu.
  446.    * Prompts for a file and restores bookmarks to those in the file.
  447.    */
  448.   onRestoreBookmarksFromFile: function PO_onRestoreBookmarksFromFile() {
  449.     var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  450.     fp.init(window, PlacesUIUtils.getString("bookmarksRestoreTitle"),
  451.             Ci.nsIFilePicker.modeOpen);
  452.     fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
  453.                     PlacesUIUtils.getString("bookmarksRestoreFilterExtension"));
  454.  
  455.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  456.                  getService(Ci.nsIProperties);
  457.     var backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
  458.     fp.displayDirectory = backupsDir;
  459.  
  460.     if (fp.show() != Ci.nsIFilePicker.returnCancel)
  461.       this.restoreBookmarksFromFile(fp.file);
  462.   },
  463.  
  464.   /**
  465.    * Restores bookmarks from a JSON file.
  466.    */
  467.   restoreBookmarksFromFile: function PO_restoreBookmarksFromFile(aFile) {
  468.     // check file extension
  469.     if (!aFile.leafName.match(/\.json$/)) {
  470.       this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreFormatError"));
  471.       return;
  472.     }
  473.  
  474.     // confirm ok to delete existing bookmarks
  475.     var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  476.                   getService(Ci.nsIPromptService);
  477.     if (!prompts.confirm(null,
  478.                          PlacesUIUtils.getString("bookmarksRestoreAlertTitle"),
  479.                          PlacesUIUtils.getString("bookmarksRestoreAlert")))
  480.       return;
  481.  
  482.     try {
  483.       PlacesUtils.restoreBookmarksFromJSONFile(aFile, [PlacesUIUtils.leftPaneFolderId]);
  484.     }
  485.     catch(ex) {
  486.       this._showErrorAlert(PlacesUIUtils.getString("bookmarksRestoreParseError"));
  487.     }
  488.   },
  489.  
  490.   _showErrorAlert: function PO__showErrorAlert(aMsg) {
  491.     var brandShortName = document.getElementById("brandStrings").
  492.                                   getString("brandShortName");
  493.  
  494.     Cc["@mozilla.org/embedcomp/prompt-service;1"].
  495.       getService(Ci.nsIPromptService).
  496.       alert(window, brandShortName, aMsg);
  497.   },
  498.  
  499.   /**
  500.    * Backup bookmarks to desktop, auto-generate a filename with a date.
  501.    * The file is a JSON serialization of bookmarks, tags and any annotations
  502.    * of those items.
  503.    */
  504.   backupBookmarks: function PO_backupBookmarks() {
  505.     var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
  506.     fp.init(window, PlacesUIUtils.getString("bookmarksBackupTitle"),
  507.             Ci.nsIFilePicker.modeSave);
  508.     fp.appendFilter(PlacesUIUtils.getString("bookmarksRestoreFilterName"),
  509.                     PlacesUIUtils.getString("bookmarksRestoreFilterExtension"));
  510.  
  511.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  512.                  getService(Ci.nsIProperties);
  513.     var backupsDir = dirSvc.get("Desk", Ci.nsILocalFile);
  514.     fp.displayDirectory = backupsDir;
  515.  
  516.     // Use YYYY-MM-DD (ISO 8601) as it doesn't contain illegal characters
  517.     // and makes the alphabetical order of multiple backup files more useful.
  518.     var date = (new Date).toLocaleFormat("%Y-%m-%d");
  519.     fp.defaultString = PlacesUIUtils.getFormattedString("bookmarksBackupFilenameJSON",
  520.                                                         [date]);
  521.  
  522.     if (fp.show() != Ci.nsIFilePicker.returnCancel) {
  523.       PlacesUtils.backupBookmarksToFile(fp.file, [PlacesUIUtils.leftPaneFolderId]);
  524.  
  525.       // copy new backup to /backups dir (bug 424389)
  526.       var latestBackup = PlacesUtils.getMostRecentBackup();
  527.       if (!latestBackup || latestBackup != fp.file) {
  528.         latestBackup.remove(false);
  529.         var date = new Date().toLocaleFormat("%Y-%m-%d");
  530.         var name = PlacesUtils.getFormattedString("bookmarksArchiveFilename",
  531.                                                   [date]);
  532.         fp.file.copyTo(this.bookmarksBackupDir, name);
  533.       }
  534.     }
  535.   },
  536.  
  537.   get bookmarksBackupDir() {
  538.     delete this.bookmarksBackupDir;
  539.     var dirSvc = Cc["@mozilla.org/file/directory_service;1"].
  540.                  getService(Ci.nsIProperties);
  541.     var bookmarksBackupDir = dirSvc.get("ProfD", Ci.nsIFile);
  542.     bookmarksBackupDir.append("bookmarkbackups");
  543.     if (!bookmarksBackupDir.exists())
  544.       bookmarksBackupDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0700);
  545.     return this.bookmarksBackupDir = bookmarksBackupDir;
  546.   },
  547.  
  548.   _paneDisabled: false,
  549.   _setDetailsFieldsDisabledState:
  550.   function PO__setDetailsFieldsDisabledState(aDisabled) {
  551.     if (aDisabled) {
  552.       document.getElementById("paneElementsBroadcaster")
  553.               .setAttribute("disabled", "true");
  554.     }
  555.     else {
  556.       document.getElementById("paneElementsBroadcaster")
  557.               .removeAttribute("disabled");
  558.     }
  559.   },
  560.  
  561.   _detectAndSetDetailsPaneMinimalState:
  562.   function PO__detectAndSetDetailsPaneMinimalState(aNode) {
  563.     /**
  564.      * The details of simple folder-items (as opposed to livemarks) or the
  565.      * of livemark-children are not likely to fill the infoBox anyway,
  566.      * thus we remove the "More/Less" button and show all details.
  567.      *
  568.      * the wasminimal attribute here is used to persist the "more/less"
  569.      * state in a bookmark->folder->bookmark scenario.
  570.      */
  571.     var infoBox = document.getElementById("infoBox");
  572.     var infoBoxExpander = document.getElementById("infoBoxExpander");
  573.     if (aNode.itemId != -1 &&
  574.         ((PlacesUtils.nodeIsFolder(aNode) &&
  575.           !PlacesUtils.nodeIsLivemarkContainer(aNode)) ||
  576.          PlacesUtils.nodeIsLivemarkItem(aNode))) {
  577.       if (infoBox.getAttribute("minimal") == "true")
  578.         infoBox.setAttribute("wasminimal", "true");
  579.       infoBox.removeAttribute("minimal");
  580.       infoBoxExpander.hidden = true;
  581.     }
  582.     else {
  583.       if (infoBox.getAttribute("wasminimal") == "true")
  584.         infoBox.setAttribute("minimal", "true");
  585.       infoBox.removeAttribute("wasminimal");
  586.       infoBoxExpander.hidden = false;
  587.     }
  588.   },
  589.  
  590.   // NOT YET USED
  591.   updateThumbnailProportions: function PO_updateThumbnailProportions() {
  592.     var previewBox = document.getElementById("previewBox");
  593.     var canvas = document.getElementById("itemThumbnail");
  594.     var height = previewBox.boxObject.height;
  595.     var width = height * (screen.width / screen.height);
  596.     canvas.width = width;
  597.     canvas.height = height;
  598.   },
  599.  
  600.   onContentTreeSelect: function PO_onContentTreeSelect() {
  601.     if (this._content.treeBoxObject.focused)
  602.       this._fillDetailsPane(this._content.selectedNode);
  603.   },
  604.  
  605.   _fillDetailsPane: function PO__fillDetailsPane(aSelectedNode) {
  606.     var infoBox = document.getElementById("infoBox");
  607.     var detailsDeck = document.getElementById("detailsDeck");
  608.  
  609.     // If a textbox within a panel is focused, force-blur it so its contents
  610.     // are saved
  611.     if (gEditItemOverlay.itemId != -1) {
  612.       var focusedElement = document.commandDispatcher.focusedElement;
  613.       if ((focusedElement instanceof HTMLInputElement ||
  614.            focusedElement instanceof HTMLTextAreaElement) &&
  615.           /^editBMPanel.*/.test(focusedElement.parentNode.parentNode.id))
  616.         focusedElement.blur();
  617.  
  618.       // don't update the panel if we are already editing this node
  619.       if (aSelectedNode && gEditItemOverlay.itemId == aSelectedNode.itemId &&
  620.           detailsDeck.selectedIndex == 1)
  621.         return;
  622.     }
  623.  
  624.     if (aSelectedNode && !PlacesUtils.nodeIsSeparator(aSelectedNode)) {
  625.       detailsDeck.selectedIndex = 1;
  626.       // Using the concrete itemId is arguably wrong. The bookmarks API
  627.       // does allow setting properties for folder shortcuts as well, but since
  628.       // the UI does not distinct between the couple, we better just show
  629.       // the concrete item properties.
  630.       if (aSelectedNode.type ==
  631.           Ci.nsINavHistoryResultNode.RESULT_TYPE_FOLDER_SHORTCUT) {
  632.         gEditItemOverlay.initPanel(asQuery(aSelectedNode).folderItemId,
  633.                                   { hiddenRows: ["folderPicker"],
  634.                                     forceReadOnly: true });
  635.       }
  636.       else {
  637.         var itemId = PlacesUtils.getConcreteItemId(aSelectedNode);
  638.         gEditItemOverlay.initPanel(itemId != -1 ? itemId :
  639.                                    PlacesUtils._uri(aSelectedNode.uri),
  640.                                    { hiddenRows: ["folderPicker"] });
  641.       }
  642.       this._detectAndSetDetailsPaneMinimalState(aSelectedNode);
  643.     }
  644.     else {
  645.       detailsDeck.selectedIndex = 0;
  646.       var selectItemDesc = document.getElementById("selectItemDescription");
  647.       var itemsCountLabel = document.getElementById("itemsCountText");
  648.       var rowCount = this._content.treeBoxObject.view.rowCount;
  649.       if (rowCount == 0) {
  650.         selectItemDesc.hidden = true;
  651.         itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.noItems");
  652.       }
  653.       else {
  654.         selectItemDesc.hidden = false;
  655.         if (rowCount == 1)
  656.           itemsCountLabel.value = PlacesUIUtils.getString("detailsPane.oneItem");
  657.         else {
  658.           itemsCountLabel.value =
  659.             PlacesUIUtils.getFormattedString("detailsPane.multipleItems",
  660.                                              [rowCount]);
  661.         }
  662.       }
  663.     }
  664.   },
  665.  
  666.   // NOT YET USED
  667.   _updateThumbnail: function PO__updateThumbnail() {
  668.     var bo = document.getElementById("previewBox").boxObject;
  669.     var width  = bo.width;
  670.     var height = bo.height;
  671.  
  672.     var canvas = document.getElementById("itemThumbnail");
  673.     var ctx = canvas.getContext('2d');
  674.     var notAvailableText = canvas.getAttribute("notavailabletext");
  675.     ctx.save();
  676.     ctx.fillStyle = "-moz-Dialog";
  677.     ctx.fillRect(0, 0, width, height);
  678.     ctx.translate(width/2, height/2);
  679.  
  680.     ctx.fillStyle = "GrayText";
  681.     ctx.mozTextStyle = "12pt sans serif";
  682.     var len = ctx.mozMeasureText(notAvailableText);
  683.     ctx.translate(-len/2,0);
  684.     ctx.mozDrawText(notAvailableText);
  685.     ctx.restore();
  686.   },
  687.  
  688.   toggleAdditionalInfoFields: function PO_toggleAdditionalInfoFields() {
  689.     var infoBox = document.getElementById("infoBox");
  690.     var infoBoxExpander = document.getElementById("infoBoxExpander");
  691.     if (infoBox.getAttribute("minimal") == "true") {
  692.       infoBox.removeAttribute("minimal");
  693.       infoBoxExpander.label = infoBoxExpander.getAttribute("lesslabel");
  694.       infoBoxExpander.accessKey = infoBoxExpander.getAttribute("lessaccesskey");
  695.     }
  696.     else {
  697.       infoBox.setAttribute("minimal", "true");
  698.       infoBoxExpander.label = infoBoxExpander.getAttribute("morelabel");
  699.       infoBoxExpander.accessKey = infoBoxExpander.getAttribute("moreaccesskey");
  700.     }
  701.   },
  702.  
  703.   /**
  704.    * Save the current search (or advanced query) to the bookmarks root.
  705.    */
  706.   saveSearch: function PO_saveSearch() {
  707.     // Get the place: uri for the query.
  708.     // If the advanced query builder is showing, use that.
  709.     var options = this.getCurrentOptions();
  710.  
  711. //@line 736 "/e/fx19rel/WINNT_5.2_Depend/mozilla/browser/components/places/content/places.js"
  712.     var queries = this.getCurrentQueries();
  713. //@line 738 "/e/fx19rel/WINNT_5.2_Depend/mozilla/browser/components/places/content/places.js"
  714.  
  715.     var placeSpec = PlacesUtils.history.queriesToQueryString(queries,
  716.                                                              queries.length,
  717.                                                              options);
  718.     var placeURI = Cc["@mozilla.org/network/io-service;1"].
  719.                    getService(Ci.nsIIOService).
  720.                    newURI(placeSpec, null, null);
  721.  
  722.     // Prompt the user for a name for the query.
  723.     // XXX - using prompt service for now; will need to make
  724.     // a real dialog and localize when we're sure this is the UI we want.
  725.     var title = PlacesUIUtils.getString("saveSearch.title");
  726.     var inputLabel = PlacesUIUtils.getString("saveSearch.inputLabel");
  727.     var defaultText = PlacesUIUtils.getString("saveSearch.defaultText");
  728.  
  729.     var prompts = Cc["@mozilla.org/embedcomp/prompt-service;1"].
  730.                   getService(Ci.nsIPromptService);
  731.     var check = {value: false};
  732.     var input = {value: defaultText};
  733.     var save = prompts.prompt(null, title, inputLabel, input, null, check);
  734.  
  735.     // Don't add the query if the user cancels or clears the seach name.
  736.     if (!save || input.value == "")
  737.      return;
  738.  
  739.     // Add the place: uri as a bookmark under the bookmarks root.
  740.     var txn = PlacesUIUtils.ptm.createItem(placeURI,
  741.                                            PlacesUtils.bookmarksMenuFolderId,
  742.                                            PlacesUtils.bookmarks.DEFAULT_INDEX,
  743.                                            input.value);
  744.     PlacesUIUtils.ptm.doTransaction(txn);
  745.  
  746.     // select and load the new query
  747.     this._places.selectPlaceURI(placeSpec);
  748.   }
  749. };
  750.  
  751. /**
  752.  * A set of utilities relating to search within Bookmarks and History.
  753.  */
  754. var PlacesSearchBox = {
  755.  
  756.   /**
  757.    * The Search text field
  758.    */
  759.   get searchFilter() {
  760.     return document.getElementById("searchFilter");
  761.   },
  762.  
  763.   /**
  764.    * Folders to include when searching.
  765.    */
  766.   _folders: [],
  767.   get folders() {
  768.     if (this._folders.length == 0)
  769.       this._folders.push(PlacesUtils.bookmarksMenuFolderId,
  770.                          PlacesUtils.unfiledBookmarksFolderId,
  771.                          PlacesUtils.toolbarFolderId);
  772.     return this._folders;
  773.   },
  774.   set folders(aFolders) {
  775.     this._folders = aFolders;
  776.     return aFolders;
  777.   },
  778.  
  779.   /**
  780.    * Run a search for the specified text, over the collection specified by
  781.    * the dropdown arrow. The default is all bookmarks, but can be
  782.    * localized to the active collection.
  783.    * @param   filterString
  784.    *          The text to search for.
  785.    */
  786.   search: function PSB_search(filterString) {
  787.     var PO = PlacesOrganizer;
  788.     // If the user empties the search box manually, reset it and load all
  789.     // contents of the current scope.
  790.     // XXX this might be to jumpy, maybe should search for "", so results
  791.     // are ungrouped, and search box not reset
  792.     if ((filterString == "" || this.searchFilter.hasAttribute("empty"))) {
  793.       PO.onPlaceSelected(false);
  794.       return;
  795.     }
  796.  
  797.     var currentOptions = PO.getCurrentOptions();
  798.     var content = PO._content;
  799.  
  800.     switch (PlacesSearchBox.filterCollection) {
  801.     case "collection":
  802.       content.applyFilter(filterString, this.folders);
  803.       // XXX changing the button text is badness
  804.       //var scopeBtn = document.getElementById("scopeBarFolder");
  805.       //scopeBtn.label = PlacesOrganizer._places.selectedNode.title;
  806.       break;
  807.     case "bookmarks":
  808.       content.applyFilter(filterString,
  809.                           [PlacesUtils.bookmarksMenuFolderId,
  810.                            PlacesUtils.toolbarFolderId,
  811.                            PlacesUtils.unfiledBookmarksFolderId]);
  812.       break;
  813.     case "history":
  814.       if (currentOptions.queryType != Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY) {
  815.         var query = PlacesUtils.history.getNewQuery();
  816.         query.searchTerms = filterString;
  817.         var options = currentOptions.clone();
  818.         options.queryType = Ci.nsINavHistoryQueryOptions.QUERY_TYPE_HISTORY;
  819.         content.load([query], options);
  820.       }
  821.       else
  822.         content.applyFilter(filterString);
  823.       break;
  824.     case "all":
  825.       content.applyFilter(filterString);
  826.       break;
  827.     }
  828.  
  829.     PlacesSearchBox.showSearchUI();
  830.     this.searchFilter.setAttribute("filtered", "true");
  831.  
  832.     // Update the details panel
  833.     PlacesOrganizer.onContentTreeSelect();
  834.   },
  835.  
  836.   /**
  837.    * Finds across all bookmarks
  838.    */
  839.   findAll: function PSB_findAll() {
  840.     this.filterCollection = "all";
  841.     this.focus();
  842.   },
  843.  
  844.   /**
  845.    * Finds in the currently selected Place.
  846.    */
  847.   findCurrent: function PSB_findCurrent() {
  848.     this.filterCollection = "collection";
  849.     this.focus();
  850.   },
  851.  
  852.   /**
  853.    * Updates the display with the title of the current collection.
  854.    * @param   title
  855.    *          The title of the current collection.
  856.    */
  857.   updateCollectionTitle: function PSB_updateCollectionTitle(title) {
  858.     if (title)
  859.       this.searchFilter.emptyText =
  860.         PlacesUIUtils.getFormattedString("searchCurrentDefault", [title]);
  861.     else
  862.       this.searchFilter.emptyText = this.filterCollection == "history" ?
  863.                                     PlacesUIUtils.getString("searchHistory") :
  864.                                     PlacesUIUtils.getString("searchBookmarks");
  865.   },
  866.  
  867.   /**
  868.    * Gets/sets the active collection from the dropdown menu.
  869.    */
  870.   get filterCollection() {
  871.     return this.searchFilter.getAttribute("collection");
  872.   },
  873.   set filterCollection(collectionName) {
  874.     this.searchFilter.setAttribute("collection", collectionName);
  875.     if (this.searchFilter.value)
  876.       return; // don't overwrite pre-existing search terms
  877.     var newGrayText = null;
  878.     if (collectionName == "collection")
  879.       newGrayText = PlacesOrganizer._places.selectedNode.title;
  880.     this.updateCollectionTitle(newGrayText);
  881.     return collectionName;
  882.   },
  883.  
  884.   /**
  885.    * Focus the search box
  886.    */
  887.   focus: function PSB_focus() {
  888.     this.searchFilter.focus();
  889.   },
  890.  
  891.   /**
  892.    * Set up the gray text in the search bar as the Places View loads.
  893.    */
  894.   init: function PSB_init() {
  895.     this.updateCollectionTitle();
  896.   },
  897.  
  898.   /**
  899.    * Gets or sets the text shown in the Places Search Box
  900.    */
  901.   get value() {
  902.     return this.searchFilter.value;
  903.   },
  904.   set value(value) {
  905.     return this.searchFilter.value = value;
  906.   },
  907.  
  908.   showSearchUI: function PSB_showSearchUI() {
  909.     // Hide the advanced search controls when the user hasn't searched
  910.     var searchModifiers = document.getElementById("searchModifiers");
  911.     searchModifiers.hidden = false;
  912.  
  913. //@line 942 "/e/fx19rel/WINNT_5.2_Depend/mozilla/browser/components/places/content/places.js"
  914.   },
  915.  
  916.   hideSearchUI: function PSB_hideSearchUI() {
  917.     var searchModifiers = document.getElementById("searchModifiers");
  918.     searchModifiers.hidden = true;
  919.   }
  920. };
  921.  
  922. /**
  923.  * Functions and data for advanced query builder
  924.  */
  925. var PlacesQueryBuilder = {
  926.  
  927.   queries: [],
  928.   queryOptions: null,
  929.  
  930. //@line 1421 "/e/fx19rel/WINNT_5.2_Depend/mozilla/browser/components/places/content/places.js"
  931.  
  932.   onScopeSelected: function PQB_onScopeSelected(aButton) {
  933.     var id = aButton.getAttribute("id");
  934.     // get scope bar
  935.     var bar = document.getElementById("organizerScopeBar");
  936.     var child = bar.firstChild;
  937.     while (child) {
  938.       if (child.getAttribute("id") != id)
  939.         child.removeAttribute("checked");
  940.       else
  941.         child.setAttribute("checked", "true");
  942.       child = child.nextSibling;
  943.     }
  944.  
  945.     // update collection type and get folders
  946.     var folders = [];
  947.     switch (id) {
  948.       case "scopeBarHistory":
  949.         PlacesSearchBox.filterCollection = "history";
  950.         folders = [];
  951.         break;
  952.       case "scopeBarFolder":
  953.         var selectedFolder = PlacesOrganizer._places.selectedNode.itemId;
  954.         // note "all bookmarks" isn't the concrete parent of the top-level
  955.         // bookmark folders
  956.         if (selectedFolder != PlacesUIUtils.allBookmarksFolderId) {
  957.           PlacesSearchBox.filterCollection = "collection";
  958.           folders.push(PlacesOrganizer._places.selectedNode.itemId);
  959.           break;
  960.         }
  961.       default: // all bookmarks
  962.         PlacesSearchBox.filterCollection = "bookmarks";
  963.         folders.push(PlacesUtils.bookmarksMenuFolderId,
  964.                      PlacesUtils.toolbarFolderId,
  965.                      PlacesUtils.unfiledBookmarksFolderId);
  966.     }
  967.  
  968.     // set scope, and re-search
  969.     PlacesSearchBox.folders = folders;
  970.     PlacesSearchBox.search(PlacesSearchBox.searchFilter.value);
  971.   }
  972. };
  973.  
  974. /**
  975.  * Population and commands for the View Menu.
  976.  */
  977. var ViewMenu = {
  978.   /**
  979.    * Removes content generated previously from a menupopup.
  980.    * @param   popup
  981.    *          The popup that contains the previously generated content.
  982.    * @param   startID
  983.    *          The id attribute of an element that is the start of the
  984.    *          dynamically generated region - remove elements after this
  985.    *          item only.
  986.    *          Must be contained by popup. Can be null (in which case the
  987.    *          contents of popup are removed).
  988.    * @param   endID
  989.    *          The id attribute of an element that is the end of the
  990.    *          dynamically generated region - remove elements up to this
  991.    *          item only.
  992.    *          Must be contained by popup. Can be null (in which case all
  993.    *          items until the end of the popup will be removed). Ignored
  994.    *          if startID is null.
  995.    * @returns The element for the caller to insert new items before,
  996.    *          null if the caller should just append to the popup.
  997.    */
  998.   _clean: function VM__clean(popup, startID, endID) {
  999.     if (endID)
  1000.       NS_ASSERT(startID, "meaningless to have valid endID and null startID");
  1001.     if (startID) {
  1002.       var startElement = document.getElementById(startID);
  1003.       NS_ASSERT(startElement.parentNode ==
  1004.                 popup, "startElement is not in popup");
  1005.       NS_ASSERT(startElement,
  1006.                 "startID does not correspond to an existing element");
  1007.       var endElement = null;
  1008.       if (endID) {
  1009.         endElement = document.getElementById(endID);
  1010.         NS_ASSERT(endElement.parentNode == popup,
  1011.                   "endElement is not in popup");
  1012.         NS_ASSERT(endElement,
  1013.                   "endID does not correspond to an existing element");
  1014.       }
  1015.       while (startElement.nextSibling != endElement)
  1016.         popup.removeChild(startElement.nextSibling);
  1017.       return endElement;
  1018.     }
  1019.     else {
  1020.       while(popup.hasChildNodes())
  1021.         popup.removeChild(popup.firstChild);
  1022.     }
  1023.     return null;
  1024.   },
  1025.  
  1026.   /**
  1027.    * Fills a menupopup with a list of columns
  1028.    * @param   event
  1029.    *          The popupshowing event that invoked this function.
  1030.    * @param   startID
  1031.    *          see _clean
  1032.    * @param   endID
  1033.    *          see _clean
  1034.    * @param   type
  1035.    *          the type of the menuitem, e.g. "radio" or "checkbox".
  1036.    *          Can be null (no-type).
  1037.    *          Checkboxes are checked if the column is visible.
  1038.    * @param   propertyPrefix
  1039.    *          If propertyPrefix is non-null:
  1040.    *          propertyPrefix + column ID + ".label" will be used to get the
  1041.    *          localized label string.
  1042.    *          propertyPrefix + column ID + ".accesskey" will be used to get the
  1043.    *          localized accesskey.
  1044.    *          If propertyPrefix is null, the column label is used as label and
  1045.    *          no accesskey is assigned.
  1046.    */
  1047.   fillWithColumns: function VM_fillWithColumns(event, startID, endID, type, propertyPrefix) {
  1048.     var popup = event.target;
  1049.     var pivot = this._clean(popup, startID, endID);
  1050.  
  1051.     // If no column is "sort-active", the "Unsorted" item needs to be checked,
  1052.     // so track whether or not we find a column that is sort-active.
  1053.     var isSorted = false;
  1054.     var content = document.getElementById("placeContent");
  1055.     var columns = content.columns;
  1056.     for (var i = 0; i < columns.count; ++i) {
  1057.       var column = columns.getColumnAt(i).element;
  1058.       var menuitem = document.createElement("menuitem");
  1059.       menuitem.id = "menucol_" + column.id;
  1060.       menuitem.column = column;
  1061.       var label = column.getAttribute("label");
  1062.       if (propertyPrefix) {
  1063.         var menuitemPrefix = propertyPrefix;
  1064.         // for string properties, use "name" as the id, instead of "title"
  1065.         // see bug #386287 for details
  1066.         var columnId = column.getAttribute("anonid");
  1067.         menuitemPrefix += columnId == "title" ? "name" : columnId;
  1068.         label = PlacesUIUtils.getString(menuitemPrefix + ".label");
  1069.         var accesskey = PlacesUIUtils.getString(menuitemPrefix + ".accesskey");
  1070.         menuitem.setAttribute("accesskey", accesskey);
  1071.       }
  1072.       menuitem.setAttribute("label", label);
  1073.       if (type == "radio") {
  1074.         menuitem.setAttribute("type", "radio");
  1075.         menuitem.setAttribute("name", "columns");
  1076.         // This column is the sort key. Its item is checked.
  1077.         if (column.getAttribute("sortDirection") != "") {
  1078.           menuitem.setAttribute("checked", "true");
  1079.           isSorted = true;
  1080.         }
  1081.       }
  1082.       else if (type == "checkbox") {
  1083.         menuitem.setAttribute("type", "checkbox");
  1084.         // Cannot uncheck the primary column.
  1085.         if (column.getAttribute("primary") == "true")
  1086.           menuitem.setAttribute("disabled", "true");
  1087.         // Items for visible columns are checked.
  1088.         if (!column.hidden)
  1089.           menuitem.setAttribute("checked", "true");
  1090.       }
  1091.       if (pivot)
  1092.         popup.insertBefore(menuitem, pivot);
  1093.       else
  1094.         popup.appendChild(menuitem);
  1095.     }
  1096.     event.stopPropagation();
  1097.   },
  1098.  
  1099.   /**
  1100.    * Set up the content of the view menu.
  1101.    */
  1102.   populateSortMenu: function VM_populateSortMenu(event) {
  1103.     this.fillWithColumns(event, "viewUnsorted", "directionSeparator", "radio", "view.sortBy.");
  1104.  
  1105.     var sortColumn = this._getSortColumn();
  1106.     var viewSortAscending = document.getElementById("viewSortAscending");
  1107.     var viewSortDescending = document.getElementById("viewSortDescending");
  1108.     // We need to remove an existing checked attribute because the unsorted
  1109.     // menu item is not rebuilt every time we open the menu like the others.
  1110.     var viewUnsorted = document.getElementById("viewUnsorted");
  1111.     if (!sortColumn) {
  1112.       viewSortAscending.removeAttribute("checked");
  1113.       viewSortDescending.removeAttribute("checked");
  1114.       viewUnsorted.setAttribute("checked", "true");
  1115.     }
  1116.     else if (sortColumn.getAttribute("sortDirection") == "ascending") {
  1117.       viewSortAscending.setAttribute("checked", "true");
  1118.       viewSortDescending.removeAttribute("checked");
  1119.       viewUnsorted.removeAttribute("checked");
  1120.     }
  1121.     else if (sortColumn.getAttribute("sortDirection") == "descending") {
  1122.       viewSortDescending.setAttribute("checked", "true");
  1123.       viewSortAscending.removeAttribute("checked");
  1124.       viewUnsorted.removeAttribute("checked");
  1125.     }
  1126.   },
  1127.  
  1128.   /**
  1129.    * Shows/Hides a tree column.
  1130.    * @param   element
  1131.    *          The menuitem element for the column
  1132.    */
  1133.   showHideColumn: function VM_showHideColumn(element) {
  1134.     var column = element.column;
  1135.  
  1136.     var splitter = column.nextSibling;
  1137.     if (splitter && splitter.localName != "splitter")
  1138.       splitter = null;
  1139.  
  1140.     if (element.getAttribute("checked") == "true") {
  1141.       column.setAttribute("hidden", "false");
  1142.       if (splitter)
  1143.         splitter.removeAttribute("hidden");
  1144.     }
  1145.     else {
  1146.       column.setAttribute("hidden", "true");
  1147.       if (splitter)
  1148.         splitter.setAttribute("hidden", "true");
  1149.     }
  1150.   },
  1151.  
  1152.   /**
  1153.    * Gets the last column that was sorted.
  1154.    * @returns  the currently sorted column, null if there is no sorted column.
  1155.    */
  1156.   _getSortColumn: function VM__getSortColumn() {
  1157.     var content = document.getElementById("placeContent");
  1158.     var cols = content.columns;
  1159.     for (var i = 0; i < cols.count; ++i) {
  1160.       var column = cols.getColumnAt(i).element;
  1161.       var sortDirection = column.getAttribute("sortDirection");
  1162.       if (sortDirection == "ascending" || sortDirection == "descending")
  1163.         return column;
  1164.     }
  1165.     return null;
  1166.   },
  1167.  
  1168.   /**
  1169.    * Sorts the view by the specified column.
  1170.    * @param   aColumn
  1171.    *          The colum that is the sort key. Can be null - the
  1172.    *          current sort column or the title column will be used.
  1173.    * @param   aDirection
  1174.    *          The direction to sort - "ascending" or "descending".
  1175.    *          Can be null - the last direction or descending will be used.
  1176.    *
  1177.    * If both aColumnID and aDirection are null, the view will be unsorted.
  1178.    */
  1179.   setSortColumn: function VM_setSortColumn(aColumn, aDirection) {
  1180.     var result = document.getElementById("placeContent").getResult();
  1181.     if (!aColumn && !aDirection) {
  1182.       result.sortingMode = Ci.nsINavHistoryQueryOptions.SORT_BY_NONE;
  1183.       return;
  1184.     }
  1185.  
  1186.     var columnId;
  1187.     if (aColumn) {
  1188.       columnId = aColumn.getAttribute("anonid")
  1189.       if (!aDirection) {
  1190.         var sortColumn = this._getSortColumn();
  1191.         aDirection = sortColumn ?
  1192.                      sortColumn.getAttribute("sortDirection") : "descending";
  1193.       }
  1194.     }
  1195.     else {
  1196.       var sortColumn = this._getSortColumn();
  1197.       columnId = sortColumn ? sortColumn.getAttribute("anonid") : "title";
  1198.     }
  1199.  
  1200.     var sortingMode;
  1201.     var sortingAnnotation = "";
  1202.     const NHQO = Ci.nsINavHistoryQueryOptions;
  1203.     switch (columnId) {
  1204.       case "title":
  1205.         sortingMode = aDirection == "descending" ?
  1206.           NHQO.SORT_BY_TITLE_DESCENDING : NHQO.SORT_BY_TITLE_ASCENDING;
  1207.         break;
  1208.       case "url":
  1209.         sortingMode = aDirection == "descending" ?
  1210.           NHQO.SORT_BY_URI_DESCENDING : NHQO.SORT_BY_URI_ASCENDING;
  1211.         break;
  1212.       case "date":
  1213.         sortingMode = aDirection == "descending" ?
  1214.           NHQO.SORT_BY_DATE_DESCENDING : NHQO.SORT_BY_DATE_ASCENDING;
  1215.         break;
  1216.       case "visitCount":
  1217.         sortingMode = aDirection == "descending" ?
  1218.           NHQO.SORT_BY_VISITCOUNT_DESCENDING : NHQO.SORT_BY_VISITCOUNT_ASCENDING;
  1219.         break;
  1220.       case "keyword":
  1221.         sortingMode = aDirection == "descending" ?
  1222.           NHQO.SORT_BY_KEYWORD_DESCENDING : NHQO.SORT_BY_KEYWORD_ASCENDING;
  1223.         break;
  1224.       case "description":
  1225.         sortingAnnotation = DESCRIPTION_ANNO;
  1226.         sortingMode = aDirection == "descending" ?
  1227.           NHQO.SORT_BY_ANNOTATION_DESCENDING : NHQO.SORT_BY_ANNOTATION_ASCENDING;
  1228.         break;
  1229.       case "dateAdded":
  1230.         sortingMode = aDirection == "descending" ?
  1231.           NHQO.SORT_BY_DATEADDED_DESCENDING : NHQO.SORT_BY_DATEADDED_ASCENDING;
  1232.         break;
  1233.       case "lastModified":
  1234.         sortingMode = aDirection == "descending" ?
  1235.           NHQO.SORT_BY_LASTMODIFIED_DESCENDING : NHQO.SORT_BY_LASTMODIFIED_ASCENDING;
  1236.         break;
  1237.       default:
  1238.         throw("Invalid Column");
  1239.     }
  1240.     result.sortingAnnotation = sortingAnnotation;
  1241.     result.sortingMode = sortingMode;
  1242.   }
  1243. };
  1244.